/**
 * @description 代理管理
 * @date 2024.5.9
 * @author wz
 * @midify 2025.5.9
 * @editor wz
 */
import { Node, Constructor, Prefab, instantiate } from "cc";
import { Singleton } from "../../utils/Singleton";
import { resMgr } from "../res/ResMgr";
import { ProxyBase } from "./ProxyBase";
import { ProxyWnd } from "./ProxyWnd";
import { SpriteAtlas } from "cc";
import { sp } from "cc";
import { SpriteFrame } from "cc";
import { CtrlData, ProxyData, ProxyMeta } from "./BaseTypes";


/**
 * 代理管理器
 */
export class ProxyMgr extends Singleton {

    /**
     * 代理元数据  ！！！要手动修改
     * key=proxyName
     */
    readonly proxyMetaMap: Map<string, ProxyMeta> = new Map();
    /**
     * 代理属性元数据  ！！！要手动修改
     * key=proxyName,subKey=propertyKey
     */
    readonly proxyPropertyMetaMap: Map<string, Map<string, CtrlData[]>> = new Map();
    /**
     * 代理的实例和预制件
     * key=proxyName
     * value=proxyData
     */
    private _proxyDataMap: Map<string, ProxyData> = new Map();


    /**
     * 检查配置
     * @param proxyClass 代理类或类名
     * @returns 是否有效
     */
    public checkConfig(proxyClass: Constructor<ProxyBase> | string): boolean {
        const proxyName = typeof proxyClass === 'string' ? proxyClass : proxyClass.name;

        if (!this.proxyMetaMap.has(proxyName)) {
            console.error(`${proxyName} - 没有使用@proxy装饰器`);
            return false;
        }

        const meta = this.proxyMetaMap.get(proxyName)!;
        if (!meta.ctor) {
            console.error(`${proxyName} - 没有界面使用@bind装饰器`);
            return false;
        }

        return true;
    }

    /**
     * 获取窗口数据
     * @param proxyName 
     * @param uuid 
     * @returns 
     */
    public getWnd(proxyName: string, uuid: string): ProxyWnd<ProxyBase> {
        const proxyData = this._proxyDataMap.get(proxyName);
        return proxyData?.wndMap.get(uuid);
    }

    /**
     * 获取默认代理数据
     * @returns 
     */
    private _getDefaultProxyData(): ProxyData {
        return {
            prefab: null,
            assetMap: new Map(),
            wndMap: new Map(),
            subWndMap: new Map(),
            loadingCount: 0
        }
    }

    /**
     * 将bundle和路径组合成一个唯一的key
     * @param bundle 
     * @param path 
     * @returns 
     */
    private _wrapBundlePath(bundle: string, path: string): string {
        return `${bundle}:${path}`
    }

    /**
     * 解析bundle和路径
     * @param key 
     * @returns 
     */
    private _parseBundlePath(key: string): { bundle: string, path: string } {
        const arr = key.split(':')
        return { bundle: arr[0], path: arr[1] }
    }

    /**
     * 创建窗口实例
     * @param proxyName 
     * @param prefab 
     * @param parentNode 
     * @returns 
     */
    private _createWndIns(proxyName: string, meta: ProxyMeta, prefab: Prefab, parentNode: Node) {
        // 实例化节点
        const node = instantiate(prefab);
        node.setParent(parentNode);
        node.setSiblingIndex(parentNode.children.length - 1);

        if (!this._proxyDataMap.has(proxyName)) {
            this._proxyDataMap.set(proxyName, this._getDefaultProxyData());
            prefab.addRef()
        }
        // 创建代理组件和窗口
        const proxy = node.getComponent(proxyName) as ProxyBase;
        const wnd = new meta.ctor(proxy, meta) as ProxyWnd<ProxyBase>;

        // 设置子窗口初始数据
        if (!this._proxyDataMap.has(proxyName)) {
            this._proxyDataMap.set(proxyName, this._getDefaultProxyData());
            // 子窗口数据自己本身需要增加引用（父窗口引用子窗口也会增加引用）
            prefab.addRef();
        }
        const proxyData = this._proxyDataMap.get(proxyName);
        proxyData.prefab = prefab
        proxyData.wndMap.set(wnd.uuid, wnd)

        // 调用初始化接口
        wnd.onInit();

        return wnd;
    }

    /**
     * 获取代理实例 (必须传入parentWnd，否则可能导致内存泄漏)
     * @param proxyClass 代理类或类名
     * @param parentNode 父节点
     * @returns 代理窗口实例
     */
    public async createWnd(proxyClass: Constructor<ProxyBase> | string, parentNode: Node): Promise<ProxyWnd<ProxyBase>> {
        const proxyName = typeof proxyClass === 'string' ? proxyClass : proxyClass.name;
        if (!this.checkConfig(proxyName)) return;
        // 初始化代理数据
        const meta = this.proxyMetaMap.get(proxyName);
        // 获取代理预制件
        const prefab = await resMgr.load(meta.path, Prefab, meta.bundle);
        return this._createWndIns(proxyName, meta, prefab, parentNode)
    }


    /**
     * 创建子窗口并关联到父窗口上
     * @param wnd 
     * @param parentNode 
     * @param parentWnd 
     * @returns 
     */
    public createSubWnd(proxyName: string, parentWnd: ProxyWnd<ProxyBase>, parentNode: Node) {
        if (!this.checkConfig(proxyName)) return;
        const meta = this.proxyMetaMap.get(proxyName)!;
        const key = this._wrapBundlePath(meta.bundle, meta.path)
        // 判断父窗口数据
        const parentProxyData = this._proxyDataMap.get(parentWnd.meta.proxyName);
        if (!parentProxyData) {
            console.error(`${proxyName}---${parentWnd.meta.proxyName}---parentProxyData is null`);
            return;
        }
        if (!parentProxyData.assetMap.has(key)) {
            console.error('父窗口没有加载此资源:' + key)
            return
        }

        // 获取窗口预制件
        const prefab = parentProxyData.assetMap.get(key) as Prefab;
        const subWnd = this._createWndIns(proxyName, meta, prefab, parentNode)

        // 和父窗口关联起来
        if (!parentProxyData.subWndMap.has(parentWnd.uuid)) {
            parentProxyData.subWndMap.set(parentWnd.uuid, [])
        }
        parentProxyData.subWndMap.get(parentWnd.uuid)!.push({ proxyName, uuid: subWnd.uuid })
        return subWnd
    }

    /**
     * 加载窗口同bundle内所需资源
     * @param wnd
     * @param path 和窗口同属bundle内的资源路径
     * @param type 加载的资源类型
     * @returns 
     */
    public async loadWndBundleRes(wnd: ProxyWnd<ProxyBase>, bundle: string, path: string, type: Constructor<SpriteFrame | SpriteAtlas | sp.SkeletonData | Prefab>) {
        const proxyData = this._proxyDataMap.get(wnd.meta.proxyName);
        if (!proxyData) {
            console.error(`getProxyRes>>>>>>>>>${wnd.meta.proxyName} not in map`);
            return
        }

        const key = this._wrapBundlePath(bundle, path);

        // 有资源的情况下直接返回
        if (proxyData.assetMap.has(key)) {
            return proxyData.assetMap.get(key);
        }

        proxyData.loadingCount++;
        const asset = await resMgr.load(path, type, bundle);
        proxyData.loadingCount--;
        if (asset && !proxyData.assetMap.has(key)) {
            asset.addRef();
            proxyData.assetMap.set(key, asset);
        }
        return asset;
    }

    /**
     * 释放代理窗口
     * @param proxyName 
     * @returns 
     */
    public releaseWnd(wnd: ProxyWnd<ProxyBase>) {
        const proxyName = wnd.meta.proxyName
        const uuid = wnd.uuid
        if (!this.checkConfig(proxyName)) return false
        // 检查代理是否有数据，和是否正在加载
        if (!this._checkProxyLoading(proxyName)) {
            return false
        }
        // 获取依赖
        const dependencyProxys = []
        this._getDependencyProxy(proxyName, dependencyProxys)
        // 销毁窗口
        this._disposeWnd(proxyName, uuid)
        // 检查能否释放
        this._checkReleaseAssets(dependencyProxys)
        return false
    }

    /**
     * 检查是否在加载资源
     * @param proxyName 
     * @returns 
     */
    private _checkProxyLoading(proxyName: string) {
        if (!this._proxyDataMap.has(proxyName)) {
            console.warn('窗口数据为空，无法检查释放条件');
            return false;
        }
        const proxyData = this._proxyDataMap.get(proxyName)
        if (proxyData.loadingCount > 0) {
            console.warn('窗口引用计数与资源数量不一致还有资源正在加载，无法检查释放条件');
            return false
        }
        return true
    }

    /**
     * 获取所有的引用到的代理
     * @param proxyName 
     * @returns 
     */
    private _getDependencyProxy(proxyName: string, dependencyProxys: string[]) {
        if (dependencyProxys.indexOf(proxyName) < 0) {
            dependencyProxys.push(proxyName)
        }
        const proxyData = this._proxyDataMap.get(proxyName)
        if (proxyData) {
            const subProxys = Array.from(proxyData.subWndMap.keys())
            for (let i = 0; i < subProxys.length; i++) {
                this._getDependencyProxy(subProxys[i], dependencyProxys)
            }
        }
    }

    /**
     * 销毁代理窗口
     * @param proxyData 
     * @param wnd 
     * @returns 
     */
    private _disposeWnd(proxyName: string, uuid: string) {
        const proxyData = this._proxyDataMap.get(proxyName)
        const wnd = proxyData.wndMap.get(uuid)
        const subWnds = proxyData.subWndMap.get(uuid) || []

        proxyData.wndMap.delete(uuid)
        proxyData.subWndMap.delete(uuid)

        for (let i = 0; i < subWnds.length; i++) {
            this._disposeWnd(subWnds[i].proxyName, subWnds[i].uuid)
        }

        // 先调用销毁函数
        wnd.onDestroy()

        // 销毁节点
        wnd['_view'].node.destroy();
        // 清理窗口对象属性
        Object.keys(wnd).forEach(key => {
            if (wnd.hasOwnProperty(key)) {
                wnd[key] = null;
            }
        });
    }


    /**
     * 检查能否释放资源
     * @param proxyNames 
     * @returns 
     */
    private _checkReleaseAssets(proxyNames: string[]) {
        for (const proxyName of proxyNames) {
            const proxyData = this._proxyDataMap.get(proxyName)
            const keys = Array.from(proxyData.wndMap.keys())
            if (keys.length <= 0) {
                proxyData.prefab.decRef()
                proxyData.assetMap.forEach(a => a.decRef())
                proxyData.assetMap.clear()
                proxyData.prefab = null
                proxyData.wndMap.clear()
                proxyData.subWndMap.clear()

            }
            this._proxyDataMap.delete(proxyName)
        }
    }

    /**
     * 获取代理元数
     * @param proxyName 
     * @returns 
     */
    public getMetaByProxy(proxyClass: Constructor<ProxyBase> | string) {
        const proxyName = typeof proxyClass == 'string' ? proxyClass : proxyClass.name
        if (!this.checkConfig(proxyName)) return
        return this.proxyMetaMap.get(proxyName)
    }
}
export const proxyMgr = ProxyMgr.getInstance<ProxyMgr>();